/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs     (C)ChaN, 2019        */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be        */
/* attached to the FatFs via a glue function rather than modifying it.   */
/* This is an example of glue functions to attach various exsisting      */
/* storage control modules to the FatFs module with a defined API.       */
/*-----------------------------------------------------------------------*/

#include "ff.h"		/* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */

#include "bsp_init.h"
#include "FlashDisk.h"

#define SPI_FLASH_ID_W25Q128 	(0xEF4018)

/* Definitions of physical drive number for each drive */
#define DEV_SPI 	0 /* Example: Map SPI Flash to physical drive 0 */
#define DEV_MMC 	1 /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB 	2 /* Example: Map USB MSD to physical drive 2 */
/* 此处盘符索引对应 ffconf.h 内 #define FF_VOLUME_STRS	"spi","sd","usb",... */

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status(
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;
	/* 必须捕获任何异步发生的媒体更改并将其反映到状态标志，否则自动挂载功能将无法正常工作。
	 * 如果系统不支持介质变化检测，应用程序需要在每次介质变化后使用f_mount函数显式地重新挂载卷。
	 */
	switch (pdrv)
	{
	case DEV_SPI:
    {
		/* result = SPI_disk_status();
		 * 一般认为 SPI-Flash 不会被物理卸载 
		 */
		stat = 0;
		return stat;
    }

	case DEV_MMC:
    {
		// result = MMC_disk_status();
		stat = 0;
		
		/* SWM34SRET6 IO-SD_DET 侦测 */
		/*
		#if (SWM34S_LCM_PCBV == SWM34SRE_PIN64_A001)
		GPIO_Init(GPIOM, PIN3, 0, 0, 0, 0);
		stat = (GPIO_GetBit(GPIOM, PIN3)) ? 0 : STA_NOINIT;
		#endif
		*/
		return stat;
    }

	case DEV_USB:
    {
        extern volatile USBH_Info_t USBH_Info;
        if(USBH_Info.State != HOST_IDLE)
        {
            uint32_t timeout = 0;
            for (timeout = 0; 
                (USBH_MSC_Info.Ready == 0) && (USBH_Info.State != HOST_IDLE) && (timeout < SystemCoreClock / 10);
                ++timeout)
            {
                __NOP(); /* 当有设备连接时, 阻塞等待挂载结果, 为避免发生硬件故障, 加入超时仲裁(具体超时数值应由用户根据自身环境测试得出) */
            }
            /*
            printf("\r\n USB Host State = [%d], USBH_MSC_Info.Ready is %s, timeout value = [%d]\r\n", 
                   USBH_Info.State, (USBH_MSC_Info.Ready != 0) ? "ok" : "no ready", timeout);
            */
        }
        stat = (USBH_MSC_Info.Ready != 0) ? 0 : STA_NOINIT;
        return stat;
    }
	}
	return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize(
	BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;

	switch (pdrv)
	{
	case DEV_SPI:
		sfc_init();
		switch (sfc_read_id())
		{
		case (SPI_FLASH_ID_W25Q128):
			printf("\r\n Identify SPI_Flash is W25Q128!\r\n");
			break;

		default:
			printf("\r\n Identify SPI_Flash is unknown!\r\n");
			// while (1) __NOP(); //需要检查硬件板级连接/ Flash 型号规格
			break;
		}
		return disk_status(pdrv);

	case DEV_MMC:
		if (SD_RES_OK == sdcard_init())
		{
			stat = disk_status(pdrv);
		}
		else
		{
			stat = STA_NOINIT; /* while(1) __NOP(); */
		}
		return stat;

	case DEV_USB:
		if (USB_State != USB_MODE_HOST_MSC) /* 仅在改变状态后执行 */
		{
			USBH_Init(&USBH_MSC_cb, &USBH_USR_cb);
			USB_State = USB_MODE_HOST_MSC;
		}
		/* 等待设备插入 */
		do
		{
			stat = disk_status(pdrv);
		} while (0); /* (stat != 0); */
		return stat;
	}
	return STA_NOINIT;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read(
	BYTE pdrv,	  /* Physical drive nmuber to identify the drive */
	BYTE *buff,	  /* Data buffer to store read data */
	LBA_t sector, /* Start sector in LBA */
	UINT count	  /* Number of sectors to read */
)
{
	DRESULT res;
	int result;

	if (disk_status(pdrv) == STA_NOINIT)
		return RES_NOTRDY;

	switch (pdrv)
	{
	case DEV_SPI:
		FlashDiskRead(sector * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE * count, buff);
		return RES_OK;

	case DEV_MMC:
		if (count == 1)
		{
			result = SDIO_BlockRead(sector, (uint32_t *)buff);
		}
		else
		{
			result = SDIO_MultiBlockRead(sector, count, (uint32_t *)buff);
		}
		return (SD_RES_OK == result) ? RES_OK : RES_ERROR;

	case DEV_USB:
		do
		{
			result = USBH_MSC_Read10(buff, sector, USBH_MSC_Info.PageSize * count);
		} while (result == USBH_MSC_BUSY);
		return (result == USBH_MSC_OK) ? RES_OK : RES_ERROR;
	}

	return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write(
	BYTE pdrv,		  /* Physical drive nmuber to identify the drive */
	const BYTE *buff, /* Data to be written */
	LBA_t sector,	  /* Start sector in LBA */
	UINT count		  /* Number of sectors to write */
)
{
	DRESULT res;
	int result;

	if (disk_status(pdrv) == STA_NOINIT)
		return RES_NOTRDY;

	switch (pdrv)
	{
	case DEV_SPI:
		FlashDiskWrite(sector * FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE * count, buff);
		return RES_OK;

	case DEV_MMC:
		if (count == 1)
		{
			result = SDIO_BlockWrite(sector, (uint32_t *)buff);
		}
		else
		{
			result = SDIO_MultiBlockWrite(sector, count, (uint32_t *)buff);
		}
		return (SD_RES_OK == result) ? RES_OK : RES_ERROR;

	case DEV_USB:
		do
		{
			result = USBH_MSC_Write10((BYTE *)buff, sector, USBH_MSC_Info.PageSize * count);
		} while (result == USBH_MSC_BUSY);
		return (result == USBH_MSC_OK) ? RES_OK : RES_ERROR;
	}

	return RES_PARERR;
}

#endif

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl(
	BYTE pdrv, /* Physical drive nmuber (0..) */
	BYTE cmd,  /* Control code */
	void *buff /* Buffer to send/receive control data */
)
{
	DRESULT res;
	int result;

	if (disk_status(pdrv) == STA_NOINIT)
		return RES_NOTRDY;

	switch (pdrv)
	{
	case DEV_SPI:
		switch (cmd)
		{
		case CTRL_FORMAT: /* 格式化物理设备 */
			// SFC_Erase(0, 1);
			// SFC_EraseEx(0xFFFFFFFF, SFC_CMD_ERASE_CHIP, 1); // 当 addr == 0xFFFFFFFF && cmd == SFC_CMD_ERASE_CHIP 时,执行片擦
			return RES_OK;

		case CTRL_SYNC:
			/* 确保设备已完成挂起的写入过程
		     * 如果磁盘 I/O 层或存储设备有回写式缓存，则脏缓存数据必须立即提交到介质
			 * 如果对介质的每次写入操作都在disk_write函数中完成，则此命令无需执行任何操作
		     */
			FlashDiskFlush();
			return RES_OK;

		case GET_SECTOR_SIZE: /* 扇区大小(通用读/写的最小数据单元) */
			*((WORD *)buff) = FLASH_SECTOR_SIZE;
			return RES_OK;

		case GET_SECTOR_COUNT:
			/* 将驱动器上的可用扇区数(允许的最大 LBA + 1)检索到buff指向的 LBA_t 变量中
			 * f_mkfs和f_fdisk函数使用此命令来确定要创建的卷/分区的大小
		     */
			*((DWORD *)buff) = DATA_FLASH_SIZE / FLASH_SECTOR_SIZE;
			return RES_OK;

		case GET_BLOCK_SIZE:
			/* 以扇区为单位将闪存介质的擦除块大小检索到 buff 指向的 DWORD 变量中 */
			*((DWORD *)buff) = FLASH_CACHE_SIZE / FLASH_SECTOR_SIZE;
			return RES_OK;

		default:
			return RES_PARERR;
		}

	case DEV_MMC:
		switch (cmd)
		{
		case CTRL_SYNC:
			// SD_DiskFlush();
			return RES_OK;

		case GET_SECTOR_SIZE:
			*((WORD *)buff) = SD_cardInfo.CardBlockSize;
			return RES_OK;

		case GET_SECTOR_COUNT:
			*((DWORD *)buff) = SD_cardInfo.CardCapacity / SD_cardInfo.CardBlockSize;
			return RES_OK;

		case GET_BLOCK_SIZE:
			*((DWORD *)buff) = 1; // SD_cardInfo.CardBlockSize / SD_cardInfo.CardBlockSize;
			return RES_OK;

		default:
			return RES_PARERR;
		}

	case DEV_USB:
		switch (cmd)
		{
		case CTRL_SYNC:
			// USBH_Process();
			return RES_OK;

		case GET_SECTOR_COUNT:
			*((DWORD *)buff) = USBH_MSC_Info.Capacity;
			return RES_OK;

		case GET_SECTOR_SIZE:
			*((WORD *)buff) = USBH_MSC_Info.PageSize;
			return RES_OK;

		case GET_BLOCK_SIZE:
			*((WORD *)buff) = USBH_MSC_Info.PageSize;
			return RES_OK;

		default:
			return RES_PARERR;
		}
	}

	return RES_PARERR;
}
